#include "program.h"

//#define LOG

int pop, gens;

const char* op_to_str(operations op) {
	switch (op) {
	case AND:
		return "AND";
	case NOT:
		return "NOT";
	case OR:
		return "OR";
	case XOR:
		return "XOR";
	case CP:
		return "CP";
	case RET:
		return "RET";
	case IF:
		return "IF";
	case INC:
		return "INC";
	case DEC:
		return "DEC";
	case GOTO:
		return "GOTO";
	}
	throw 0;
}

void memory::print() {
	cout << endl << "IN:" << endl;
	for (int n = 0; n < IN_LEN; n++)
		cout << input[n];
	cout << endl << "WORK:" << endl;
	for (int n = 0; n < WORK_LEN; n++)
		cout << work[n];
	cout << endl << "OUT:" << endl;
	for (int n = 0; n < OUT_LEN; n++)
		cout << output[n];
	cout << endl;
}

instruction::instruction(bool rnd) {
	if (rnd) {
		op = (operations) (rand() % NOPS);
		if (op != GOTO && op != IF) {
			op1 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
			op2 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
		} else if (op == GOTO) {
			op1 = rand() % CODE_LEN;
			op2 = -1;
		} else { // IF
			op1 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
			op2 = rand() % CODE_LEN;
		}
	} else {
		res = op1 = op2 = -1;
		op = NOPS;
	}
}

instruction::instruction(const instruction& a, bool mutate) {
	op = a.op;
	op1 = a.op1;
	op2 = a.op2;
	if (mutate) {
		int r = rand() % 3;
		if (!r) {
			op = (operations) (rand() % NOPS);
			op1 = op2 = -1;
		} else if (r == 1) {
			if (op != GOTO)
				op1 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
			else
				op1 = rand() % CODE_LEN;
		} else {
			if (op != IF)
				op2 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
			else
				op2 = rand() % CODE_LEN;
		}
	}
	if (op1 == NOPS)
		op1 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
	if (op2 == NOPS)
		op2 = rand() % (IN_LEN + WORK_LEN + OUT_LEN);
}
	int counter;

instruction::instruction(operations _op, short _op1, short _op2, short _res) :
		op(_op), op1(_op1), op2(_op2), res(_res) {
}

void instruction::Do(char* mem, int& ip) {
	++ip;
	switch (op) {
	case AND:
		mem[res] = DO_AND(mem[op1], mem[op2]);
		break;
	case OR:
		mem[res] = DO_OR(mem[op1], mem[op2]);
		break;
	case XOR:
		mem[res] = DO_XOR(mem[op1], mem[op2]);
		break;
	case NOT:
		mem[res] = DO_NOT(mem[op1]);
		break;
	case CP:
		mem[op2] = mem[op1];
		break;
	case RET:
		ip = CODE_LEN;
		break;
	case IF:
		if (mem[op1])
			ip = op2;
		break;
	case INC:
		mem[op1]++;
		break;
	case DEC:
		mem[op1]--;
		break;
	case GOTO:
		ip = op1;
		break;
	default:
		throw 0;
	}
#ifdef LOG
	cout << "ip: " << ip << " op: " << op_to_str(op) << " op1: " << op1
	<< " op2: " << op2 << " res: " << res;
	((memory*) mem)->print();
#endif
}

result program::run(const char* in, short len) {
	score = -HUGE_VAL;
	int ip = 0;
	memcpy(mem.input, in, len);
	counter = 0;
	while (ip < CODE_LEN && ++counter < 1000)
		instructions[ip].Do(mem.input, ip);
	return result(&mem);
}

double program::train(const char* in, short inlen, const char* out,
		short outlen, result* outr) {
	const char* rout = run(in, inlen).mem->output;
	score = -counter;
	for (int n = 0; n < CODE_LEN; n++)
		if (instructions[n].op != RET)
			score--;
	for (int n = 0; n < outlen; n++)
		if (rout[n] == out[n])
			score += 25;
		else
			score -= 25;
	if (outr)
		*outr = result(&mem, score);
	return score;
}

bool program::comprog(const program& a, const program& b) {
	return a.score < b.score;
}

int main(int, const char** argc) {
	program p;
	pop = atoi(argc[1]);
	gens = atoi(argc[2]);
	int c = 0;
	p.instructions[c++] = instruction(INC, 0, -1, IN_LEN + WORK_LEN);
	p.instructions[c++] = instruction(INC, 1, -1, IN_LEN + WORK_LEN + 1);
	p.instructions[c++] = instruction(INC, 2, -1, IN_LEN + WORK_LEN + 2);
	p.instructions[c++] = instruction(CP, 0, IN_LEN + WORK_LEN);
	p.instructions[c++] = instruction(CP, 1, IN_LEN + WORK_LEN + 1);
	p.instructions[c++] = instruction(CP, 2, IN_LEN + WORK_LEN + 2);
	//p.instructions[c++] = instruction(CP, 3, IN_LEN + WORK_LEN + 3);
	//p.instructions[c++] = instruction(CP, 4, IN_LEN + WORK_LEN + 4);
	p.instructions[c++] = instruction(CP, 5, IN_LEN + WORK_LEN + 5);
	p.instructions[c++] = instruction(CP, 6, IN_LEN + WORK_LEN + 6);
	p.instructions[c++] = instruction(CP, 7, IN_LEN + WORK_LEN + 7);
	p.instructions[c++] = instruction(RET);
//	for (int n = 0; n < 1e+8; n++) {
//		/*cout << */p.run("abcdefgh", 8) /*<< endl;*/;
//	}
	program* ps = new program[pop];
	double s, hs = -HUGE_VAL;
	result out;
	for (int n = 0; n < pop; n++)
		ps[n] = p;
	for (int g = 0; g < gens; g++) {
		if (g % (gens / 1000) == 0)
			cout << g << " " << hs << " ";
		for (int n = 0; n < pop; n++) {
			if (n == 103)
				n = 103;
			ps[n].mutate();
			s = ps[n].train("abcdefgh", 8, "bcddefgh", 8, &out);
			if (g % (gens / 1000) == 0)
				if (n % (pop / 20) == 0)
					cout << s << ' ';
//			if (n % 1000 == 0)
//				cout << n << " " << s << " " << hs << " " << out << endl;
			hs = max(s, hs);
		}
		if (g % (gens / 1000) == 0)
			cout << endl;
		sort(ps, ps + pop - 1, program::comprog);
		for (int n = 0; n < pop / 4; n++) {
//			cout << ps[n].score << " <- " << ps[3 * pop / 4 + n].score << ",";
			ps[n] = ps[3 * pop / 4 + n];
			ps[n].mutate();
		}
		if (g % (gens / 1000) == 0)
			cout << ps[pop - 1].run("abcdefgh", 8).mem->output << endl;
	}
	//cout << p.train("abcdefgh", 8, "bcddefgh", 8) << endl;
	return 0;
}

